#ifndef SCENEELEMENT_HEADER_VIZMF_PROJECT
#define SCENEELEMENT_HEADER_VIZMF_PROJECT

#include "vector3.h"
#include "matrix4.h"
#include "material.h"
#include "id_generator.h"
#include "output_device_visitor_interface.h"
#include <iostream>

class SceneElement {
public:
  virtual ~SceneElement() {}
  typedef IdGenerator::id_type id_type;
  typedef float float_type;
  SceneElement(id_type id) : id_(id) {}
  SceneElement(id_type id, const Vector3& coord, const Material& m) : id_(id), col_(m), c_(coord) {}
  void setCoord(const Vector3& coord) { c_ = coord; }
  const Vector3& coord() const { return c_; }
  void setMaterial(const Material& c) { col_ = c; }
  const Material& material() const { return col_; }
  id_type id() const { return id_; } 
  
  virtual void visit(OutputDeviceVisitorInterface& v) { v.visit(this); }
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  { 
    strm << "SceneElement: " << std::endl <<
            "  id " << id() << std::endl << 
            "  center " << coord() << std::endl << 
            "  color " << material() << std::endl << 
            "--------------------------------" << std::endl; 
  }
#endif

private:
  id_type id_;
  Material col_;
  Vector3 c_;
};

#ifdef DEBUG
inline std::ostream& operator << (std::ostream& strm, const SceneElement& e)
{
  e.printMe(strm); return strm;
}
#endif

class UnorientableObject : public SceneElement
{
public:
  UnorientableObject(const id_type id, const Vector3& coord, const Material& m, const SceneElement::float_type r)
    : SceneElement(id, coord, m), r_(r) {}
  Matrix4 matrix() const
  {
    Matrix4 result;
    result.loadIdentity();
    result.scale(Vector3(r_, r_, r_));
    result.translate(coord());
    return result;
  }
  SceneElement::float_type radius() const {return r_;}
  void setRadius(SceneElement::float_type r) { r_ = r;}
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  {
    strm << "UnorientableObject: " << std::endl <<
            "  radius " << radius() << std::endl;
    SceneElement::printMe(strm);
  }
#endif
private:
  SceneElement::float_type r_;
};

class OrientableObject : public SceneElement
{
public:
  OrientableObject(const id_type id, const Vector3& coord, const Material& m, const Vector3 Xaxis, const Vector3 Yaxis, const Vector3 Zaxis, const Vector3 scale_factors)
    : SceneElement(id, coord, m), X_(Xaxis), Y_(Yaxis), Z_(Zaxis), S_(scale_factors) {}
  OrientableObject(const id_type id, const Vector3& coord, const Material& m, const Vector3 axis, const Vector3 scale_factors)
    : SceneElement(id, coord, m), Z_(axis), S_(scale_factors)
  {
    Z_.normalize();
    Y_ = Z_.perpendicular();
    Y_.normalize();
    X_ = Y_.perpendicular(Z_);
  }
    
  const Vector3& X() const { return X_; }
  const Vector3& Y() const { return Y_; }
  const Vector3& Z() const { return Z_; }
  const Vector3& scale() const { return S_; }
  
  void orientX(const Vector3& axis)
  {
    X_ = axis;
    X_.normalize();
    Y_ = X_.perpendicular();
    Y_.normalize();
    Z_ = X_.perpendicular(Y_);
  }
  void setScaleX(const float_type f) {S_[0]=f;}
  void setScaleY(const float_type f) {S_[1]=f;}
  void setScaleZ(const float_type f) {S_[2]=f;}
  
  Matrix4 matrix() const { return Matrix4(X_, Y_, Z_, S_, coord());}
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  { 
    strm << "OrientableObject: " << std::endl <<
            "  matrix " << matrix() << std::endl;
    SceneElement::printMe(strm);
  }
#endif
private:
  Vector3 X_, Y_, Z_, S_;
};

class Sphere : public UnorientableObject 
{
public:
  Sphere(const SceneElement::id_type id, const Vector3& center, float_type radius, const Material& c) 
    : UnorientableObject(id, center, c, radius) {}

  virtual void visit(OutputDeviceVisitorInterface& v) { v.visit(this); }
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  { 
    strm << "Sphere: " << std::endl;
    UnorientableObject::printMe(strm); 
  }
#endif
};

class Ellipsoid : public OrientableObject
{
public:
  Ellipsoid(const SceneElement::id_type id, const Vector3& center,
          const Vector3 Xaxis, const Vector3 Yaxis, const Vector3 Zaxis, const Vector3 scale_factors, const Material& c)
    : OrientableObject(id, center, c, Xaxis, Yaxis, Zaxis, scale_factors) {}
  virtual void visit(OutputDeviceVisitorInterface& v) { v.visit(this); }
    
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  { 
    strm << "Ellipsoid: " << std::endl;
    OrientableObject::printMe(strm); 
  }
#endif
};

class Cylinder : public OrientableObject
{
public:
  Cylinder(const SceneElement::id_type id, const Vector3& pos, const float_type length, const float_type radius, const Vector3& axis, const Material& c)
    : OrientableObject(id, pos, c, axis, Vector3(radius, radius, length)) {}
  virtual void visit(OutputDeviceVisitorInterface& v) { v.visit(this); }

  void setLength(const float_type l) { setScaleX(l); }
  float_type length() const { return scale()[0]; }
  void setRadius(const float_type r) { setScaleY(r); setScaleZ(r); }
  float_type radius() const { return scale()[1]; }
  void setAxis(const Vector3& x) { orientX(x); }
  const Vector3& axis() const { return X(); }
  
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  { 
    strm << "Cylinder: " << std::endl;
    OrientableObject::printMe(strm); 
  }
#endif
};

class Cone : public Cylinder
{
public:
  Cone(const SceneElement::id_type id, const Vector3& base, float_type length, float_type radius, const Vector3& axis, const Material& c)
    : Cylinder(id, base, length, radius, axis, c) {}
  virtual void visit(OutputDeviceVisitorInterface& v) { v.visit(this); }
    
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  { 
    strm << "Cone: " << std::endl;
    OrientableObject::printMe(strm); 
  }
#endif
};

class Paralelepiped : public OrientableObject
{
public:
  Paralelepiped(const SceneElement::id_type id, const Vector3& center,
          const Vector3 Xaxis, const Vector3 Yaxis, const Vector3 Zaxis, const Vector3 scale_factors, const Material& c)
    : OrientableObject(id, center, c, Xaxis, Yaxis, Zaxis, scale_factors) {}
  virtual void visit(OutputDeviceVisitorInterface& v) { v.visit(this); }
    
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  { 
    strm << "Paralelepiped: " << std::endl;
    OrientableObject::printMe(strm); 
  }
#endif
};

// for now it's just a stub
class Surface : public SceneElement {
public:
  Surface(const SceneElement::id_type id, const Vector3& center) : SceneElement(id, center, Material()) {}
  virtual void visit(OutputDeviceVisitorInterface& v) { v.visit(this); }
  
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  { 
    strm << "Surface: " << std::endl;
    SceneElement::printMe(strm); 
  }
#endif

protected:
//  Mesh mesh_;
// etc.
};

class Point : public UnorientableObject
{
public:
  Point(const SceneElement::id_type id, const Vector3& vec, const Material& c) 
    : UnorientableObject(id, vec, c, 0.1) {}
  virtual void visit(OutputDeviceVisitorInterface& v) { v.visit(this); }
    
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  { 
    strm << "Point: " << std::endl;
    UnorientableObject::printMe(strm); 
  }
#endif
};

class Text : public UnorientableObject
{
public:
  Text(const SceneElement::id_type id, const Vector3& vec, const Material& c, const std::string& text)
    : UnorientableObject(id, vec, c, 1.0), t(text) {}
  virtual void visit(OutputDeviceVisitorInterface& v) { v.visit(this); }
  const std::string& text() const { return t; }
  void setText(const std::string& text) { t = text; }
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  {
    strm << "Text: " << t << std::endl;
    UnorientableObject::printMe(strm);
  }
#endif
private:
  std::string t;
};

class Line : public OrientableObject
{
public:
  Line(const SceneElement::id_type id, const Vector3& from, const Vector3& to, const Material& c) 
    : OrientableObject(id, (from+to)/2, c, from-to, Vector3(0.1, 0.1, (from-to).length()) ), vec1_(from), vec2_(to) {}

  Vector3 from() const {return vec1_;}
  Vector3 to() const {return vec2_;}
  virtual void visit(OutputDeviceVisitorInterface& v) { v.visit(this); }
  
#ifdef DEBUG
  virtual void printMe (std::ostream& strm) const
  { 
    strm << "Line: " << std::endl <<
            "  " << vec1_ << " - " << vec2_ << std::endl;
    OrientableObject::printMe(strm);
  }
#endif

protected:
  Vector3 vec1_, vec2_;
};

#endif // SCENEELEMENT_HEADER_VIZMF_PROJECT
